using System;
using System.Collections.Generic;
using ServiceStack.DesignPatterns.Command;
using ServiceStack.Logging;
using ServiceStack.LogicFacade;

namespace ServiceStack.Common.Support
{
	public abstract class LogicFacadeBase : ILogicFacade
	{
		private readonly ILog log = LogManager.GetLogger(typeof(LogicFacadeBase));

		internal class InitialisationContext : IInitContext
		{
			private readonly LogicFacadeBase logicFacade;

			/// <summary>
			/// Gets or sets the object that has been initialized only.
			/// </summary>
			public object InitialisedObject
			{
				get;
				set;
			}

			/// <summary>
			/// Determines whether this context is initialise only or not
			/// </summary>
			internal readonly InitOptions initOptions;

			/// <summary>
			/// Constructs a new InitialiseOnlyContext
			/// </summary>
			internal InitialisationContext(LogicFacadeBase logicFacade, InitOptions options)
			{
				this.logicFacade = logicFacade;
				this.initOptions = options;
			}

			/// <summary>
			/// Call to remove this current context and reveal the previous context (if any).
			/// </summary>
			public virtual void Dispose()
			{
				this.logicFacade.contexts.Pop();
			}
		}

		/// <summary>
		/// Gets the current context (or null if none).
		/// </summary>
		private InitialisationContext CurrentContext
		{
			get
			{
				//TODO: check if '|| this.contexts.Count == 0)' is intended as it was throwing an exception
				if (this.contexts == null || this.contexts.Count == 0)
				{
					return null;
				}

				return this.contexts.Peek();
			}
		}

		[ThreadStatic]
		internal Stack<InitialisationContext> contexts;

		/// <summary>
		/// Checks if the current context is set to "initialize only".
		/// </summary>
		public bool IsCurrentlyInitializeOnly
		{
			get
			{
				return this.CurrentContext != null
					   && ((int)(this.CurrentContext.initOptions & InitOptions.InitialiseOnly) != 0);
			}
		}

		public IInitContext AcquireInitContext(InitOptions initOptions)
		{
			if (this.contexts == null)
			{
				this.contexts = new Stack<InitialisationContext>();
			}

			var context = new InitialisationContext(this, initOptions);

			this.contexts.Push(context);

			return context;
		}

		/// <summary>
		/// Executes the specified action.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="action">The action.</param>
		/// <returns></returns>
		protected T Execute<T>(ICommand<T> action)
		{
			try
			{
				DateTime before = DateTime.Now;

				this.log.DebugFormat("Executing action '{0}'", action.GetType().Name);

				Init(action);

				if (this.CurrentContext != null)
				{
					this.CurrentContext.InitialisedObject = action;
				}

				if (this.IsCurrentlyInitializeOnly)
				{
					this.log.DebugFormat("Action '{0}' not executed (InitializedOnlyContext).", action.GetType().Name);
					return default(T);
				}
				else
				{
					T result = action.Execute();

					TimeSpan timeTaken = DateTime.Now - before;
					this.log.DebugFormat("Action '{0}' executed. Took {1} ms.", action.GetType().Name, timeTaken.TotalMilliseconds);

					return result;
				}

			}
			catch (Exception ex)
			{
				log.ErrorFormat("Error executing action", ex);
				throw;
			}
		}

		protected abstract void Init<T>(ICommand<T> action);

		public virtual void Dispose() { }
	}
}